package com.yunxi.poc.starbucks.service.impl;

import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.yunxi.poc.starbucks.common.BizConstant;
import com.yunxi.poc.starbucks.entity.*;
import com.yunxi.poc.starbucks.service.*;
import com.yunxi.poc.starbucks.util.CacheUtil;
import com.yunxi.poc.starbucks.vo.StoreRateVO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.Collectors;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author shiyuan.xlc
 * @since 2021-08-12
 */
@Service
public class BuyServiceImpl implements IBuyService {

    private final static Logger logger = LoggerFactory.getLogger(BuyServiceImpl.class);

    @Resource
    ISatisfiedService satisfiedService;
    @Resource
    IScrowdedService crowdedService;
    @Resource
    IPunishmentService punishmentService;
    @Resource
    ITb1StoreService tb1StoreService;
    @Resource
    ITb1StoreDataService tb1StoreDataService;
    @Resource
    IOrderService orderService;
    @Resource
    ITOrderInputService orderInputService;
    @Resource
    ITOrderNewService orderNewService;
    @Resource
    ITb1StoreOpenService storeOpenService;
    @Resource
    ITOrderNewDetailsService orderNewDetailsService;
    @Resource
    ITb1TransactionSaleService transactionSaleService;

    @Override
    public List<StoreRateVO> storeRatings(double buyerLon, double buyerLat, LocalDateTime buyTime, String storeId, TOrderNew orderNew) {
        // 根据坐标获取门店列表（电子围栏）
        List<String> ids = tb1StoreDataService.searchGeoShape(buyerLon, buyerLat);

        // 保存电子围栏过滤门店
        orderNew.setStoresFilterGeoshapeNum(ids.size());
        orderNew.setStoresFilterGeoshape(ids.toString());

        // 增加指定门店
        if (StringUtils.hasLength(storeId) && !ids.contains(storeId)) ids.add(storeId);

        // 限定门店范围
        if (BizConstant.STORES_LIMIT) {
            ids = ids.parallelStream().filter(BizConstant.TARGET_STORES::contains).collect(Collectors.toList());
        }

        if (CollectionUtils.isEmpty(ids)) {
            orderNew.setCalStatus("WARN");
            orderNew.setCalMsg(orderNew.getCalMsg() + " no stores nearby ");
            return new ArrayList<>();
        }

        // 过滤不在营业时间内的门店
//        QueryWrapper<Tb1StoreOpen> openWrapper = new QueryWrapper<>();
//        openWrapper.in("global_id", ids);
//        openWrapper.eq("day_of_week_mon1_sun7", buyTime.getDayOfWeek().getValue());
//        openWrapper.le("business_open_time", buyTime.toLocalTime());
//        openWrapper.ge("business_close_time", buyTime.toLocalTime());
//        List<Tb1StoreOpen> storeOpens = storeOpenService.list(openWrapper);
        List<String> finalIds = ids;
        List<String> openStoreIds = CacheUtil.storeOpens.stream()
                .filter(
                        e -> finalIds.contains(e.getGlobalId())
                                && buyTime.getDayOfWeek().getValue() == e.getDayOfWeekMon1Sun7()
                                && (buyTime.toLocalTime().compareTo(e.getBusinessOpenTime()) >= 0)
                                && (buyTime.toLocalTime().compareTo(e.getBusinessCloseTime()) <= 0))
                .map(Tb1StoreOpen::getGlobalId)
                .distinct()
                .collect(Collectors.toList());

        if (CollectionUtils.isEmpty(openStoreIds)) {
            orderNew.setCalStatus("WARN");
            orderNew.setCalMsg(orderNew.getCalMsg() + " no open stores ");
            return new ArrayList<>();
        }

//        List<String> openStoreIds = storeOpens.stream().map(e -> e.getGlobalId()).collect(Collectors.toList());

        // TODO 根据当时营业状态过滤门店
//        QueryWrapper<Tb1Store> QueryWrapper = new QueryWrapper<>();
//        QueryWrapper.in("global_store_number", openStoreIds);
//        QueryWrapper.eq("valid_flag", 1);
//        List<Tb1Store> stores = tb1StoreService.list(QueryWrapper);
        List<Tb1Store> stores = CacheUtil.stores.stream()
                .filter(e -> openStoreIds.contains(e.getGlobalStoreNumber()))
                .collect(Collectors.toList());

        if (CollectionUtils.isEmpty(stores)) {
            orderNew.setCalStatus("WARN");
            orderNew.setCalMsg(orderNew.getCalMsg() + " no valid stores ");
            return new ArrayList<>();
        }

        orderNew.setStoresFilterOpenedNum(openStoreIds.size());
        orderNew.setStoresFilterOpened(openStoreIds.toString());
        orderNew.setStoresFilterValidNum(stores.size());
        orderNew.setStoresFilterValid(stores.stream().map(Tb1Store::getGlobalStoreNumber).collect(Collectors.toList()).toString());

        // 打分计算
        List<StoreRateVO> storeRates = new ArrayList<>();
        for (Tb1Store store : stores) {
            StoreRateVO storeRate = new StoreRateVO();

            storeRate.setStoreName(store.getStoreName());
            storeRate.setGlobalStoreNumber(store.getGlobalStoreNumber());
            storeRate.setStoreAddress(store.getStoreAddress());
            storeRate.setTag("SUCCESS");
            storeRate.setRate(99999.0);

            // 拥挤度计算
            double crowdedScore = crowdedService.scoreScrowded(store, buyTime, storeRate);
            storeRate.setScrowdedScore(crowdedScore);

            // 客户满意度计算
            if (ObjectUtils.isEmpty(store.getLongitude()) || ObjectUtils.isEmpty(store.getLatitude())) {
                storeRate.setTag("WARN");
                storeRate.getMsg().add("store has no longitude or latitude");
                storeRates.add(storeRate);
                continue;
            }
            double satisfiedScore = satisfiedService.scoreSatisfied(buyerLon, buyerLat, store, storeRate);
            storeRate.setSatisfiedScore(satisfiedScore);

            // TODO 惩罚项不参与计算
            // 惩罚项计算
            // double punishmentScore = punishmentService.scorePunishment(buyTime, store, stores, storeRate);
            // storeRate.setPunishmentScore(punishmentScore);

            // 打分
            double rate = crowdedScore
                    + BizConstant.SATISFIED_LAMBDA1 * satisfiedScore
                    /*+ BizConstant.PUNISHMENT_LAMBDA2 * punishmentScore*/;
            rate = new BigDecimal(rate).setScale(6, RoundingMode.DOWN).doubleValue();
            storeRate.setRate(rate);

            storeRates.add(storeRate);
        }
        storeRates.sort(Comparator.comparing(StoreRateVO::getRate));

        return storeRates;
    }

    @Override
    public void batchMockBuyByStores(LocalDateTime start, LocalDateTime end, String[] storeIds) {
        for (String storeId : storeIds) {
            mockByOrders(start, end, storeId, new String[]{});
        }
    }

    @Override
    public void batchMockBuyByOrders(String[] orderIds) {
        mockByOrders(null, null, null, orderIds);
    }

    @Override
    public void batchMockBuyByInputOrders() {
        mockByOrdersInput();
    }

    private void mockByOrders(LocalDateTime start, LocalDateTime end, String storeId, String[] orderIds) {
        long size = BizConstant.BATCH_NUM, currentPage = 0, total, done = 0;

        QueryWrapper<Order> queryWrapper = new QueryWrapper<>();
        if (orderIds.length > 0) {
            queryWrapper.in("id", orderIds);
        } else {
            queryWrapper.eq("store_id", storeId);
            queryWrapper.ge("create_date", start);
            queryWrapper.le("create_date", end);
        }
        queryWrapper.eq("channel", "MOD");  // 限定输入类型
        queryWrapper.eq("status", "5");     // 已完成
        queryWrapper.eq("type", "1");       // 正常订单
        queryWrapper.orderByAsc("create_date");
        Page<Order> pageOrders = orderService.page(new Page<>(++currentPage, size, true), queryWrapper);

        total = pageOrders.getTotal();
        logger.info("Task running ->" + " storeId: " + storeId + " start: " + start + " end: " + end
                + " orderIds: "
                + " total: " + total
                + " done: " + done);

        while (pageOrders.getRecords().size() > 0) {
            List<Order> orders = pageOrders.getRecords();

            List<TOrderNew> ordersNew = Collections.synchronizedList(new ArrayList<>());

            orders.parallelStream().forEach((order) -> {
                TOrderNew orderNew = new TOrderNew();
                BeanUtils.copyProperties(order, orderNew);
                this.calScore(orderNew);
                ordersNew.add(orderNew);
            });

            if (ordersNew.size() > 0) {
                orderNewService.saveBatch(ordersNew);
                orderNewDetailsService.batchGenDetails(ordersNew);
                done += ordersNew.size();
            }

            logger.info("Task running ->" + " storeId: " + storeId + " start: " + start + " end: " + end
                    + " orderIds: "
                    + " total: " + total
                    + " done: " + done);

            pageOrders = orderService.page(new Page<>(++currentPage, size, false), queryWrapper);
        }
    }

    private void mockByOrdersInput() {
        long size = BizConstant.BATCH_NUM;
        long currentPage = 0;
        long total;
        AtomicLong done = new AtomicLong();

        QueryWrapper<TOrderInput> queryWrapper = new QueryWrapper<>();
        queryWrapper.orderByAsc("create_date");
        Page<TOrderInput> pageOrders = orderInputService.page(new Page<>(++currentPage, size, true), queryWrapper);

        total = pageOrders.getTotal();
        logger.info("Task running ->" + " total: " + total);

        while (pageOrders.getRecords().size() > 0) {
            List<TOrderInput> orders = pageOrders.getRecords();

            List<TOrderNew> ordersNew = Collections.synchronizedList(new ArrayList<>());

            // 不能并行执行，因为前后订单的有依赖，需要顺序执行
            orders//.parallelStream()
                    .forEach((order) -> {
                        TOrderNew orderNew = new TOrderNew();
                        BeanUtils.copyProperties(order, orderNew);
                        this.calScore(orderNew);
                        ordersNew.add(orderNew);
                        logger.info("Task running ->" + " total: " + total + " done: " + done.incrementAndGet());
                    });

            if (ordersNew.size() > 0) {
                orderNewService.saveBatch(ordersNew);
                orderNewDetailsService.batchGenDetails(ordersNew);
            }

            pageOrders = orderInputService.page(new Page<>(++currentPage, size, false), queryWrapper);
        }
    }

    private void calScore(TOrderNew orderNew) {
        orderNew.setCalStatus("SUCCESS");

        if (!StringUtils.hasLength(orderNew.getLongitude()) || !StringUtils.hasLength(orderNew.getLatitude())) {
            orderNew.setCalStatus("WARN");
            orderNew.setCalMsg(orderNew.getCalMsg() + " null longitude or latitude ");
            return;
        }

        List<StoreRateVO> ratesRes = this.storeRatings(
                Double.parseDouble(orderNew.getLongitude()), Double.parseDouble(orderNew.getLatitude()),
                orderNew.getCreateDate(), orderNew.getStoreId(),
                orderNew);

        if (ObjectUtils.isEmpty(ratesRes)) {
        } else {
            orderNew.setTrace(JSONUtil.parseArray(ratesRes).toStringPretty());

            // 过滤异常门店
            List<StoreRateVO> rates = ratesRes.stream()
                    .filter((e) -> e.getTag().equals("SUCCESS"))
                    .collect(Collectors.toList());

            if (CollectionUtils.isEmpty(rates)) {
                orderNew.setCalStatus("WARN");
                orderNew.setCalMsg(orderNew.getCalMsg() + " no success result ");
                return;
            }

            // 新店
            StoreRateVO betterStore = rates.get(0);
            // 原店
            StoreRateVO storeRateOrg = ratesRes.stream().filter((e) -> e.getGlobalStoreNumber().equals(orderNew.getStoreId())).findFirst().orElse(null);

            if (!ObjectUtils.isEmpty(betterStore)) {
                // 新店 和 原店 分数相同，选择原店
//                betterStore = !ObjectUtils.isEmpty(storeRateOrg) && Double.doubleToLongBits(betterStore.getRate()) == Double.doubleToLongBits(storeRateOrg.getRate())
//                        ? storeRateOrg : betterStore;
                if (!ObjectUtils.isEmpty(storeRateOrg) && Double.doubleToLongBits(betterStore.getRate()) == Double.doubleToLongBits(storeRateOrg.getRate())) {
                    betterStore = storeRateOrg;
                } else {
                    StoreRateVO finalBetterStore = betterStore;
                    List<StoreRateVO> equalList = rates.parallelStream()
                            .filter(e -> Double.doubleToLongBits(e.getRate()) == Double.doubleToLongBits(finalBetterStore.getRate()))
                            .collect(Collectors.toList());
                    Random rand = new Random();
                    betterStore = equalList.get(rand.nextInt(equalList.size()));
                }

                orderNew.setNewId(betterStore.getGlobalStoreNumber());
                orderNew.setNewName(betterStore.getStoreName());
                orderNew.setNewTag(betterStore.getTag());
                orderNew.setNewMsg(String.valueOf(betterStore.getMsg()));
                // 新店-总分
                orderNew.setNewScore(String.valueOf(betterStore.getRate()));
                // 新店-拥挤度
                orderNew.setNewScrowdedScore(String.valueOf(betterStore.getScrowdedScore()));
                orderNew.setNewScrowdedFinalOrderId(String.valueOf(betterStore.getScrowdedFinalOrderId()));
                orderNew.setNewScrowdedFinalOrderCreateDate(String.valueOf(betterStore.getScrowdedFinalOrderCreateDate()));
                orderNew.setNewScrowdedFinalOrderMakeFinishDate(String.valueOf(betterStore.getScrowdedFinalOrderMakeFinishDate()));
                orderNew.setNewScrowdedFinalOrderUndoneNum(String.valueOf(betterStore.getScrowdedFinalOrderUndoneNum()));
                orderNew.setNewScrowdedFinalOrderUntilNowMins(String.valueOf(betterStore.getScrowdedFinalOrderUntilNowMins()));
                orderNew.setNewScrowdedEfficiency(String.valueOf(betterStore.getScrowdedEfficiency()));
                orderNew.setNewScrowdedCoffeeQueue(String.valueOf(betterStore.getScrowdedCoffeeQueue()));
                orderNew.setNewScrowdedRecentOrdersProductNum(String.valueOf(betterStore.getScrowdedRecentOrdersProductNum()));
                orderNew.setNewScrowdedFinalOrderMakeCostTime(String.valueOf(betterStore.getScrowdedFinalOrderMakeCostTime()));
                orderNew.setNewScrowdedQueueInitNum(String.valueOf(betterStore.getScrowdedQueueInitNum()));
                orderNew.setNewScrowdedQueueStartAt(String.valueOf(betterStore.getScrowdedQueueStartAt()));
                orderNew.setNewScrowdedQueueEndAt(String.valueOf(betterStore.getScrowdedQueueEndAt()));
                orderNew.setNewScrowdedStroreOpenAt(String.valueOf(betterStore.getScrowdedStoreOpenAt()));
                // 新店-满意度
                orderNew.setNewSatisfiedScore(String.valueOf(betterStore.getSatisfiedScore()));
                orderNew.setNewSatisfiedDistanceX(String.valueOf(betterStore.getSatisfiedDistanceX()));
                orderNew.setNewSatisfiedDistanceY(String.valueOf(betterStore.getSatisfiedDistanceY()));
                orderNew.setNewSatisfiedDistanceDirection(betterStore.getSatisfiedDistanceDirection());
                orderNew.setNewSatisfiedLon(String.valueOf(betterStore.getSatisfiedLon()));
                orderNew.setNewSatisfiedLat(String.valueOf(betterStore.getSatisfiedLat()));
                // 新店-惩罚项
                orderNew.setNewPunishmentScore(String.valueOf(betterStore.getPunishmentScore()));
                orderNew.setNewPunishmentLast24hCount(String.valueOf(betterStore.getPunishmentLast24hCount()));
                orderNew.setNewPunishmentLast24hOtherCount(String.valueOf(betterStore.getPunishmentLast24hOtherCount()));
                orderNew.setNewPunishmentLast24hPercent(String.valueOf(betterStore.getPunishmentLast24hPercent()));
                orderNew.setNewPunishmentLast7daysCount(String.valueOf(betterStore.getPunishmentLast7daysCount()));
                orderNew.setNewPunishmentLast7daysOtherCount(String.valueOf(betterStore.getPunishmentLast7daysOtherCount()));
                orderNew.setNewPunishmentLast7daysPercent(String.valueOf(betterStore.getPunishmentLast7daysPercent()));

                // 重新选店后的数据，进入 tb1_transaction_sale 作为后续计算的队列数据
                if (BizConstant.SCROWDED_QUEUE_SWITCH) {
                    Tb1TransactionSale tb1TransactionSale = new Tb1TransactionSale();
                    tb1TransactionSale.setCity(String.valueOf(orderNew.getId()));       // 模拟订单数据，取值 t_order.id
                    tb1TransactionSale.setRn(6);                                        // 6-表示模拟订单数据
                    tb1TransactionSale.setSaleItemCode(BizConstant.SCROWDED_QUEUE_MOCK_SALE_ITEM_CODE);
                    tb1TransactionSale.setSaleItemQuantity(BigDecimal.valueOf(BizConstant.SCROWDED_QUEUE_MOCK_SALE_ITEM_QUANTITY));
                    tb1TransactionSale.setSaleDatetime(orderNew.getCreateDate());
                    try {
                        tb1TransactionSale.setGlobalStoreNumber(Integer.valueOf(betterStore.getGlobalStoreNumber()));
                    } catch (Exception e) {
                        logger.error(e.getLocalizedMessage());
                    }
                    transactionSaleService.save(tb1TransactionSale);
                }
            }

            if (!ObjectUtils.isEmpty(storeRateOrg)) {
                orderNew.setOrgTag(String.valueOf(storeRateOrg.getTag()));
                orderNew.setOrgMsg(String.valueOf(storeRateOrg.getMsg()));
                // 原店-总分
                orderNew.setOrgScore(String.valueOf(storeRateOrg.getRate()));
                // 原店-拥挤度
                orderNew.setOrgScrowdedScore(String.valueOf(storeRateOrg.getScrowdedScore()));
                orderNew.setOrgScrowdedFinalOrderId(String.valueOf(storeRateOrg.getScrowdedFinalOrderId()));
                orderNew.setOrgScrowdedFinalOrderCreateDate(String.valueOf(storeRateOrg.getScrowdedFinalOrderCreateDate()));
                orderNew.setOrgScrowdedFinalOrderMakeFinishDate(String.valueOf(storeRateOrg.getScrowdedFinalOrderMakeFinishDate()));
                orderNew.setOrgScrowdedFinalOrderUndoneNum(String.valueOf(storeRateOrg.getScrowdedFinalOrderUndoneNum()));
                orderNew.setOrgScrowdedFinalOrderUntilNowMins(String.valueOf(storeRateOrg.getScrowdedFinalOrderUntilNowMins()));
                orderNew.setOrgScrowdedEfficiency(String.valueOf(storeRateOrg.getScrowdedEfficiency()));
                orderNew.setOrgScrowdedCoffeeQueue(String.valueOf(storeRateOrg.getScrowdedCoffeeQueue()));
                orderNew.setOrgScrowdedRecentOrdersProductNum(String.valueOf(storeRateOrg.getScrowdedRecentOrdersProductNum()));
                orderNew.setOrgScrowdedFinalOrderMakeCostTime(String.valueOf(storeRateOrg.getScrowdedFinalOrderMakeCostTime()));
                orderNew.setOrgScrowdedQueueInitNum(String.valueOf(storeRateOrg.getScrowdedQueueInitNum()));
                orderNew.setOrgScrowdedQueueStartAt(String.valueOf(storeRateOrg.getScrowdedQueueStartAt()));
                orderNew.setOrgScrowdedQueueEndAt(String.valueOf(storeRateOrg.getScrowdedQueueEndAt()));
                orderNew.setOrgScrowdedStroreOpenAt(String.valueOf(storeRateOrg.getScrowdedStoreOpenAt()));
                // 原店-满意度
                orderNew.setOrgSatisfiedScore(String.valueOf(storeRateOrg.getSatisfiedScore()));
                orderNew.setOrgSatisfiedDistanceX(String.valueOf(storeRateOrg.getSatisfiedDistanceX()));
                orderNew.setOrgSatisfiedDistanceY(String.valueOf(storeRateOrg.getSatisfiedDistanceY()));
                orderNew.setOrgSatisfiedDistanceDirection(storeRateOrg.getSatisfiedDistanceDirection());
                orderNew.setOrgSatisfiedLon(String.valueOf(storeRateOrg.getSatisfiedLon()));
                orderNew.setOrgSatisfiedLat(String.valueOf(storeRateOrg.getSatisfiedLat()));
                // 原店-惩罚项
                orderNew.setOrgPunishmentScore(String.valueOf(storeRateOrg.getPunishmentScore()));
                orderNew.setOrgPunishmentLast24hCount(String.valueOf(storeRateOrg.getPunishmentLast24hCount()));
                orderNew.setOrgPunishmentLast24hOtherCount(String.valueOf(storeRateOrg.getPunishmentLast24hOtherCount()));
                orderNew.setOrgPunishmentLast24hPercent(String.valueOf(storeRateOrg.getPunishmentLast24hPercent()));
                orderNew.setOrgPunishmentLast7daysCount(String.valueOf(storeRateOrg.getPunishmentLast7daysCount()));
                orderNew.setOrgPunishmentLast7daysOtherCount(String.valueOf(storeRateOrg.getPunishmentLast7daysOtherCount()));
                orderNew.setOrgPunishmentLast7daysPercent(String.valueOf(storeRateOrg.getPunishmentLast7daysPercent()));
            }
        }
    }
}